package repository

import (
	"context"
	"errors"
	"time"
	"webook/article/domain"
	"webook/article/repository/cache"
	"webook/article/repository/dao"
	"webook/pkg/logger"
)

type ArticleRepository interface {
	Create(ctx context.Context, art domain.Article) (int64, error)
	Update(ctx context.Context, art domain.Article) error
	List(ctx context.Context, author int64, offset int, limit int) ([]domain.Article, error)

	// Sync 本身要求先保存到制作库，再同步到线上库
	Sync(ctx context.Context, art domain.Article) (int64, error)
	// SyncStatus 仅仅同步状态
	SyncStatus(ctx context.Context, uid, id int64, status domain.ArticleStatus) error
	GetById(ctx context.Context, id int64) (domain.Article, error)

	GetPublishedById(ctx context.Context, id int64) (domain.Article, error)
	ListPub(ctx context.Context, utime time.Time, offset int, limit int) ([]domain.Article, error)
}

type CachedArticleRepository struct {
	// 操作单一的库
	dao   dao.ArticleDAO
	cache cache.ArticleCache
	l     logger.LoggerV1
}

func NewArticleRepository(dao dao.ArticleDAO, c cache.ArticleCache, l logger.LoggerV1) ArticleRepository {
	return &CachedArticleRepository{
		dao:   dao,
		l:     l,
		cache: c,
	}
}

func (repo *CachedArticleRepository) Create(ctx context.Context, art domain.Article) (int64, error) {
	id, err := repo.dao.Insert(ctx, repo.toEntity(art))
	if err != nil {
		return 0, err
	}
	author := art.Author.Id
	err = repo.cache.DelFirstPage(ctx, author)
	if err != nil {
		repo.l.Error("删除缓存失败", logger.Int64("author", author), logger.Error(err))
	}
	return id, err
}

func (repo *CachedArticleRepository) Update(ctx context.Context, art domain.Article) error {
	return repo.dao.UpdateById(ctx, repo.toEntity(art))
}

func (repo *CachedArticleRepository) List(ctx context.Context, author int64, offset int, limit int) ([]domain.Article, error) {
	// 只有第一页才走缓存，并且假定一页只有 100 条
	// 也就是说，如果前端允许创作者调整页的大小
	// 那么只有 100 这个页大小这个默认情况下，会走索引
	if offset == 0 && limit == 100 {
		arts, err := repo.cache.GetFirstPage(ctx, author)
		if err == nil {
			go func() {
				repo.preCache(ctx, arts)
			}()
			return arts, err
		}
		if !errors.Is(err, cache.ErrKeyNotExist) {
			repo.l.Error("查询缓存失败", logger.Int64("author", author), logger.Error(err))
		}
	}

	// 没有缓存
	arts, err := repo.dao.GetByAuthor(ctx, author, offset, limit)
	if err != nil {
		return []domain.Article{}, err
	}

	domainArts := make([]domain.Article, len(arts))

	for i, art := range arts {
		domainArts[i] = repo.ToDomain(art)
	}

	go func() {
		repo.preCache(ctx, domainArts)
	}()

	go func() {
		//设置缓存
		err = repo.cache.SetFirstPage(ctx, author, domainArts)
		if err != nil {
			repo.l.Error("刷新第一页缓存失败", logger.Int64("author", author), logger.Error(err))
		}
	}()

	return domainArts, err
}

func (repo *CachedArticleRepository) Sync(ctx context.Context, art domain.Article) (int64, error) {
	id, err := repo.dao.Sync(ctx, repo.toEntity(art))
	if err != nil {
		return 0, err
	}
	art.Id = id
	go func() {
		author := art.Author.Id
		err = repo.cache.DelFirstPage(ctx, author)
		if err != nil {
			repo.l.Error("删除第一页缓存失败", logger.Int64("author", author), logger.Error(err))
		}
		err = repo.cache.SetPub(ctx, art)
		if err != nil {
			repo.l.Error("提前设置缓存失败", logger.Int64("author", author), logger.Error(err))
		}
	}()
	return id, nil
}

func (repo *CachedArticleRepository) SyncStatus(ctx context.Context, uid, id int64, status domain.ArticleStatus) error {
	return repo.dao.SyncStatus(ctx, uid, id, status.ToUint8())
}

func (repo *CachedArticleRepository) GetById(ctx context.Context, id int64) (domain.Article, error) {
	res, err := repo.dao.GetById(ctx, id)
	return repo.ToDomain(res), err
}

func (repo *CachedArticleRepository) GetPublishedById(ctx context.Context, id int64) (domain.Article, error) {
	res, err := repo.dao.GetPubById(ctx, id)
	return repo.ToDomain(dao.Article(res)), err
}

func (repo *CachedArticleRepository) ListPub(ctx context.Context, utime time.Time, offset int, limit int) ([]domain.Article, error) {
	arts, err := repo.dao.ListPubByUtime(ctx, utime, offset, limit)
	if err != nil {
		return []domain.Article{}, err
	}
	domainArts := make([]domain.Article, len(arts))
	for i, art := range arts {
		domainArts[i] = repo.ToDomain(dao.Article(art))
	}
	return domainArts, nil
}

func (repo *CachedArticleRepository) ToDomain(art dao.Article) domain.Article {
	return domain.Article{
		Id:      art.Id,
		Title:   art.Title,
		Status:  domain.ArticleStatus(art.Status),
		Content: art.Content,
		Author: domain.Author{
			Id: art.AuthorId,
		},
	}
}

// 提前将 List 的第一篇文章的所有内容缓存到 redis
func (repo *CachedArticleRepository) preCache(ctx context.Context, arts []domain.Article) {
	// 1MB
	const contentSizeThreshold = 1024 * 1024
	if len(arts) > 0 && len(arts[0].Content) <= contentSizeThreshold {
		err := repo.cache.Set(ctx, arts[0])
		if err != nil {
			repo.l.Error("提前准备缓存失败", logger.Error(err))
		}
	}
}

func (repo *CachedArticleRepository) toEntity(art domain.Article) dao.Article {
	return dao.Article{
		Id:       art.Id,
		Title:    art.Title,
		Content:  art.Content,
		AuthorId: art.Author.Id,
		// 这一步，就是将领域状态转化为存储状态。
		// 这里我们就是直接转换，
		// 有些情况下，这里可能是借助一个 map 来转
		Status: uint8(art.Status),
	}
}
